/**
 * File:   modbus_client_channel_view_model.c
 * Author: AWTK Develop Team
 * Brief:  modbus_client_channel_view_model
 *
 * Copyright (c) 2023 - 2024  Guangzhou ZHIYUAN Electronics Co.,Ltd.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * License file for more details.
 *
 */

/**
 * History:
 * ================================================================
 * 2024-3-26 Li XianJing <xianjimli@hotmail.com> created
 *
 */

#include "tkc/utils.h"
#include "tkc/object.h"
#include "conf_io/conf_node.h"
#include "tkc/func_call_parser.h"

#include "hmi_model.h"
#include "hmi_service.h"
#include "history_data.h"
#include "modbus_init_req.h"
#include "modbus_client_channel.h"
#include "modbus_client_view_model.h"
#include "modbus_client_channel_view_model.h"

#include "mvvm/mvvm.h"

#define MODBUS_CLIENT_CHANNEL_MODEL(obj) modbus_client_channel_model_cast(obj)

typedef struct _modbus_client_channel_model_t {
  tk_object_t object;
  /**
   * @property {tk_object_t*} modbus_model
   * @annotation ["readable"]
   * modbus_model
   */
  tk_object_t* modbus;
  /**
   * @property {const char*} name
   * @annotation ["readable"]
   * name
   */
  char* name;
  /**
   * @property {uint32_t} items
   * @annotation ["readable"]
   * items
   */
  uint32_t items;
  /**
   * @property {tk_object_t*} variables
   * @annotation ["readable"]
   * variables
   */
  tk_object_t* variables;
  /**
   * @property {modbus_prop_t} prop
   * @annotation ["readable"]
   * 用来保存channel的基本信息。
   */
  modbus_prop_t prop;
} modbus_client_channel_model_t;

static modbus_client_channel_model_t* modbus_client_channel_model_cast(tk_object_t* obj);

ret_t modbus_client_channel_view_model_parse_prop(tk_object_t* obj, const char* name,
                                                  modbus_prop_t* prop) {
  uint32_t index = 0;
  uint32_t item_size = 0;
  const char* subname = NULL;
  modbus_prop_t* named_prop = NULL;
  modbus_client_channel_model_t* channel = MODBUS_CLIENT_CHANNEL_MODEL(obj);
  return_value_if_fail(obj != NULL && name != NULL && prop != NULL, RET_BAD_PARAMS);
  return_value_if_fail(*name == '[' && channel != NULL, RET_BAD_PARAMS);

  index = tk_atoi(name + 1);
  return_value_if_fail(index < channel->items, RET_BAD_PARAMS);

  subname = strstr(name, "].");
  return_value_if_fail(subname != NULL, RET_BAD_PARAMS);

  subname += 2;
  named_prop = (modbus_prop_t*)tk_object_get_prop_pointer(channel->variables, subname);

  item_size = channel->prop.buffer_length / channel->items;
  return_value_if_fail(item_size > 0, RET_BAD_PARAMS);

  if (named_prop != NULL) {
    *prop = *named_prop;
    prop->buffer = channel->prop.buffer + item_size * index;
    prop->buffer_length = item_size;

    return RET_OK;
  } else {
    char prop_value[128] = {0};
    snprintf(prop_value, sizeof(prop_value), "%s.%s", channel->name, subname);
    if (modbus_client_view_model_parse_prop(channel->modbus, prop_value, prop) == RET_OK) {
      prop->buffer = channel->prop.buffer + item_size * index;
      prop->buffer_length = item_size;
      return RET_OK;
    }
  }

  return RET_NOT_FOUND;
}

static ret_t modbus_client_channel_load_variables(modbus_client_channel_model_t* channel,
                                                  conf_node_t* node) {
  value_t v;
  char prop_value[128] = {0};
  modbus_prop_t* prop = NULL;
  conf_node_t* iter = conf_node_get_first_child(node);

  while (iter != NULL) {
    const char* name = conf_node_get_name(iter);

    if (conf_node_get_value(iter, &v) == RET_OK) {
      snprintf(prop_value, sizeof(prop_value), "%s.%s", channel->name, value_str(&v));
      prop = modbus_prop_create(NULL, prop_value);

      log_debug("%s = %s\n", name, value_str(&v));
      if (prop != NULL) {
        tk_object_set_prop_pointer_ex(channel->variables, name, prop,
                                      (tk_destroy_t)modbus_prop_destroy);
      } else {
        log_debug("modbus_prop_create failed:%s\n", name);
      }
    }

    iter = iter->next;
  }

  return RET_OK;
}

static ret_t modbus_client_channel_load_conf(modbus_client_channel_model_t* channel,
                                             conf_doc_t* doc) {
  conf_node_t* node = NULL;
  return_value_if_fail(channel != NULL && doc != NULL, RET_BAD_PARAMS);

  channel->items = conf_doc_get_int(doc, "items", 1);
  channel->name = tk_strdup(conf_doc_get_str(doc, "channel", ""));

  node = conf_node_find_child(doc->root, "variables");
  if (node != NULL) {
    modbus_client_channel_load_variables(channel, node);
  }

  return RET_OK;
}

static ret_t modbus_client_model_set_prop(tk_object_t* obj, const char* name, const value_t* v) {
  modbus_prop_t prop;
  modbus_client_channel_model_t* channel = MODBUS_CLIENT_CHANNEL_MODEL(obj);
  return_value_if_fail(obj != NULL && name != NULL && v != NULL, RET_BAD_PARAMS);

  if (tk_str_ieq(name, MODBUS_CLIENT_CHANNEL_MODEL_PROP_CONNECTED)) {
    return RET_NOT_FOUND;
  } else if (tk_str_ieq(name, MODBUS_CLIENT_CHANNEL_MODEL_PROP_URL)) {
    return RET_NOT_FOUND;
  } else if (tk_str_ieq(name, TK_OBJECT_PROP_SIZE)) {
    channel->items = value_uint32(v);
    return RET_OK;
  }

  if (modbus_client_channel_view_model_parse_prop(obj, name, &prop) == RET_OK) {
    return modbus_client_model_set_prop_impl(channel->modbus, &prop, v);
  } else {
    log_debug("modbus_client_channel_view_model_parse_prop failed:%s\n", name);
  }

  return RET_NOT_FOUND;
}

static ret_t modbus_client_model_get_prop(tk_object_t* obj, const char* name, value_t* v) {
  modbus_prop_t prop;
  modbus_client_channel_model_t* channel = MODBUS_CLIENT_CHANNEL_MODEL(obj);
  return_value_if_fail(obj != NULL && name != NULL && v != NULL, RET_BAD_PARAMS);

  if (tk_str_ieq(name, MODBUS_CLIENT_CHANNEL_MODEL_PROP_CONNECTED)) {
    return tk_object_get_prop(channel->modbus, name, v);
  } else if (tk_str_ieq(name, MODBUS_CLIENT_CHANNEL_MODEL_PROP_URL)) {
    return tk_object_get_prop(channel->modbus, name, v);
  } else if (tk_str_eq(name, TK_OBJECT_PROP_SIZE)) {
    value_set_int(v, channel->items);
    return RET_OK;
  } else if (tk_str_eq(name, "name")) {
    value_set_str(v, channel->name);
    return RET_OK;
  }

  if (modbus_client_channel_view_model_parse_prop(obj, name, &prop) == RET_OK) {
    return modbus_client_model_get_prop_impl(channel->modbus, &prop, v);
  } else {
    log_debug("modbus_client_channel_view_model_parse_prop failed:%s\n", name);
  }

  return RET_NOT_FOUND;
}

static ret_t modbus_client_model_on_destroy(tk_object_t* obj) {
  modbus_client_channel_model_t* channel = MODBUS_CLIENT_CHANNEL_MODEL(obj);
  return_value_if_fail(channel != NULL, RET_BAD_PARAMS);

  TK_OBJECT_UNREF(channel->variables);
  TKMEM_FREE(channel->name);

  return RET_OK;
}

static bool_t modbus_client_model_can_exec(tk_object_t* obj, const char* name, const char* args) {
  modbus_client_channel_model_t* channel = MODBUS_CLIENT_CHANNEL_MODEL(obj);
  return_value_if_fail(obj != NULL && name != NULL, FALSE);

  if (tk_str_ieq(name, MODBUS_CLIENT_CHANNEL_MODEL_CMD_UPDATE)) {
    return channel->modbus != NULL;
  }

  return FALSE;
}

static ret_t modbus_client_model_exec(tk_object_t* obj, const char* name, const char* args) {
  modbus_client_channel_model_t* channel = MODBUS_CLIENT_CHANNEL_MODEL(obj);
  return_value_if_fail(obj != NULL && name != NULL, RET_BAD_PARAMS);

  if (tk_str_ieq(name, MODBUS_CLIENT_CHANNEL_MODEL_CMD_UPDATE)) {
    return tk_object_exec(channel->modbus, name, channel->name);
  }

  return RET_NOT_FOUND;
}

static const object_vtable_t s_modbus_client_channel_vtable = {
    .type = "modbus_client_channel",
    .desc = "modbus_client_channel",
    .is_collection = FALSE,
    .size = sizeof(modbus_client_channel_model_t),
    .on_destroy = modbus_client_model_on_destroy,
    .get_prop = modbus_client_model_get_prop,
    .set_prop = modbus_client_model_set_prop,
    .can_exec = modbus_client_model_can_exec,
    .exec = modbus_client_model_exec,
};

static const object_vtable_t s_modbus_client_channel_array_vtable = {
    .type = "modbus_client_channel",
    .desc = "modbus_client_channel",
    .is_collection = TRUE,
    .size = sizeof(modbus_client_channel_model_t),
    .on_destroy = modbus_client_model_on_destroy,
    .get_prop = modbus_client_model_get_prop,
    .set_prop = modbus_client_model_set_prop,
    .can_exec = modbus_client_model_can_exec,
    .exec = modbus_client_model_exec,
};

static tk_object_t* modbus_client_channel_model_create(conf_doc_t* doc) {
  tk_object_t* obj = NULL;
  modbus_client_channel_model_t* channel = NULL;
  return_value_if_fail(doc != NULL, NULL);

  obj = object_create(&s_modbus_client_channel_vtable);
  return_value_if_fail(obj != NULL, NULL);
  channel = (modbus_client_channel_model_t*)obj;

  channel->variables = object_default_create();
  return_value_if_fail(channel->variables != NULL, NULL);

  if (modbus_client_channel_load_conf(channel, doc) == RET_OK) {
    if (channel->items > 1) {
      obj->vt = &s_modbus_client_channel_array_vtable;
    } else {
      obj->vt = &s_modbus_client_channel_vtable;
    }
  
    return obj;
  } else {
    TK_OBJECT_UNREF(obj);
  }

  return NULL;
}

static modbus_client_channel_model_t* modbus_client_channel_model_cast(tk_object_t* obj) {
  return_value_if_fail(obj != NULL && obj->vt != NULL, NULL);
  return_value_if_fail(obj->vt->get_prop == s_modbus_client_channel_vtable.get_prop, NULL);

  return (modbus_client_channel_model_t*)obj;
}

tk_object_t* modbus_client_channel_view_model_create_with_name(const char* name) {
  conf_doc_t* doc = NULL;
  tk_object_t* obj = NULL;
  char config_name[MAX_PATH + 1] = {0};
  tk_snprintf(config_name, MAX_PATH, "%s.json", name);
  doc = hmi_model_load_conf_from_asset(config_name);

  if (doc != NULL) {
    obj = modbus_client_channel_model_create(doc);
    conf_doc_destroy(doc);
    tk_object_set_name(obj, name);
    return obj;
  }

  return NULL;
}

view_model_t* modbus_client_channel_view_model_create(navigator_request_t* req) {
  const char* type_and_args = NULL;
  view_model_object_wrapper_t* modbus_client_wrapper = NULL;
  return_value_if_fail(req != NULL && req->parent_view_model != NULL, NULL);
  modbus_client_wrapper = VIEW_MODEL_OBJECT_WRAPPPER(req->parent_view_model);
  return_value_if_fail(modbus_client_wrapper != NULL, NULL);

  type_and_args = tk_object_get_prop_str(req->args, NAVIGATOR_ARG_VIEW_MODEL_TYPE);
  if (type_and_args != NULL && strchr(type_and_args, '(') != NULL) {
    tk_object_t* obj = NULL;
    view_model_t* vm = NULL;
    char prop[128] = {0};
    tk_object_t* args = func_call_parse(type_and_args, tk_strlen(type_and_args));
    const char* config = tk_object_get_prop_str(args, "name");
    modbus_client_channel_model_t* channel = NULL;

    obj = modbus_client_channel_view_model_create_with_name(config);
    TK_OBJECT_UNREF(args);
    return_value_if_fail(obj != NULL, NULL);

    channel = MODBUS_CLIENT_CHANNEL_MODEL(obj);
    channel->modbus = modbus_client_wrapper->obj;

    /*获取channel的信息*/
    snprintf(prop, sizeof(prop), "%s.byte[0]", channel->name);
    modbus_client_view_model_parse_prop(channel->modbus, prop, &channel->prop);

    if (channel->items > 1) {
      vm = view_model_array_object_wrapper_create(obj);
    } else {
      vm = view_model_object_wrapper_create(obj);
    }
    tk_object_set_name(TK_OBJECT(vm), obj->name);

    return vm;
  } else {
    log_debug("invalid params:%s\n", type_and_args);
  }

  return NULL;
}
